/*
    Copyright (C) 2021 Huawei Device Co., Ltd.
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */

package com.danikula.videocache.onefileStrategy;

import com.danikula.videocache.Constants;
import com.danikula.videocache.ProxyCacheException;
import com.danikula.videocache.file.DiskUsage;
import com.danikula.videocache.file.Files;
import com.danikula.videocache.file.UnlimitedDiskUsage;
import com.danikula.videocache.interfacers.IOneFileCache;

import com.danikula.videocache.util.LogUtil;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import ohos.agp.utils.TextTool;
import ohos.data.preferences.Preferences;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;

import static com.danikula.videocache.Constants.TEMP_POSTFIX;

/**
 * 分节点缓存具体实现类
 */
public class OneFileCache extends IOneFileCache {
    private static final String TAG = "FileCache";
    private Preferences preferences;
    private long cacheStart = 0;
    private long cacheEnd = Long.MAX_VALUE;
    private long totalWriteSize = 0;
    private boolean isStop = false;
    private long fileLength = Long.MAX_VALUE;
    private long[][] cacheArr;

    public OneFileCache(File file) throws ProxyCacheException {
        this(file, new UnlimitedDiskUsage());
    }

    public OneFileCache(File file, DiskUsage diskUsage) throws ProxyCacheException {
        try {
            if (diskUsage == null) {
                throw new NullPointerException();
            }
            this.diskUsage = diskUsage;
            File directory = file.getParentFile();
            Files.makeDir(directory);
            boolean completed = file.exists();
            this.file = completed ? file : new File(file.getParentFile(), file.getName() + TEMP_POSTFIX);
            this.dataFile = new RandomAccessFile(this.file, completed ? "r" : "rws");
        } catch (IOException e) {
            throw new ProxyCacheException("Error using file " + file + " as disc cache", e);
        }
    }

    public long getFileLength() {
        return fileLength;
    }

    public void setPreferences(Preferences preferences) {
        this.preferences = preferences;
    }

    public void setFileLength(long fileLength) {
        try {
            if (fileLength <= 0 || this.fileLength == fileLength) {
                return;
            }
            synchronized (dataFile) {
                this.fileLength = fileLength;
                if (dataFile.length() != fileLength) {
                    // 重新设置缓存文件的长度为视频长度
                    dataFile.setLength(fileLength);
                }
            }
            if (this.cacheEnd == 0 || this.cacheEnd > fileLength) {
                cacheEnd = fileLength;
                // 第一次的时候 将视频长度存入sp
                addNewAreaToSP(fileLength, fileLength);
            }
            long fileflegth = preferences.getLong(Constants.CACHE_FILE_LENGTH, 0);
            if (fileflegth == 0) {
                preferences.putLong(Constants.CACHE_FILE_LENGTH, fileLength).flush();
            }
        } catch (IOException e) {
            LogUtil.error(TAG, "resize file length fail:" + e.toString());
        }
    }

    /**
     * 初始化缓存起止点 缓存长度
     */
    public void initDownLoadData() {
        cacheStart = 0;
        cacheEnd = 0;
        totalWriteSize = 0;
    }

    /**
     * 设置缓存起始点
     *
     * @param cacheStart 缓存起始点
     */
    public void setCacheStart(long cacheStart) {
        this.cacheStart = cacheStart;
    }

    /**
     * 设置缓存终止点
     *
     * @param cacheEnd 缓存终止点
     */
    public void setCacheEnd(long cacheEnd) {
        this.cacheEnd = cacheEnd;
        if (this.cacheEnd <= 0) {
            this.cacheEnd = Long.MAX_VALUE;
        }
        if (fileLength > 0 && this.cacheEnd > fileLength) {
            this.cacheEnd = fileLength;
        }
    }

    /**
     * 获取需要网络请求数据的起止点
     *
     * @param offset 播放器当前位置
     * @return 需要网络请求数据的起止点
     */
    public long[] getDownloadArea(long offset) {
        if (!isTempFile(file)) {
            return new long[]{0, fileLength};
        }
        cacheArr = getSpCacheArr();
        if (cacheArr == null) {
            return new long[]{0, fileLength};
        } else {
            return Solution.getDownloadArea(offset, fileLength, cacheArr);
        }
    }

    private long[][] getSpCacheArr() {
        if (preferences == null) {
            return new long[][]{new long[]{0, fileLength}};
        }
        String gson = preferences.getString(Constants.VIDEO_CACHE_SP, "");
        if (TextTool.isNullOrEmpty(gson)) {
            if (fileLength > 0 && fileLength != Long.MAX_VALUE) {
                long[][] tempArr = new long[][]{new long[]{0, 0}, new long[]{fileLength,
                        fileLength}};
                preferences.putString(Constants.VIDEO_CACHE_SP, new Gson().toJson(tempArr)).flush();
            } else {
                long[][] tempArr = new long[][]{new long[]{0, 0}};
                preferences.putString(Constants.VIDEO_CACHE_SP, new Gson().toJson(tempArr)).flush();
            }

            return new long[][]{new long[]{0, fileLength}};
        } else {
            return new Gson().fromJson(gson, new TypeToken<long[][]>() {
            }.getType());
        }
    }

    @Override
    public synchronized long available() throws ProxyCacheException {
        return getTotalCacheLength();
    }

    @Override
    public synchronized void complete() throws ProxyCacheException {
        if (!isCompleted() || !isTempFile(file)) {
            return;
        }
        renameFile();
    }

    @Override
    public synchronized boolean isCompleted() throws ProxyCacheException {
        return getTotalCacheLength() == fileLength && fileLength > 0;
    }

    /**
     * Returns file to be used fo caching. It may as original file passed in constructor as some temp file for not
     * completed cache.
     *
     * @return file for caching.
     */
    public File getFile() {
        return file;
    }

    @Override
    public int read(byte[] buffer, long offset, int length) throws ProxyCacheException {
        synchronized (dataFile) {
            try {
                // 数据已缓存 只读缓存
                dataFile.seek(offset);
                return dataFile.read(buffer, 0, length);
            } catch (IOException e) {
                String format = "Error reading %d bytes with offset %d from file[%d bytes] to buffer[%d bytes]";
                throw new ProxyCacheException(String.format(format, length, offset, available(), buffer.length), e);
            }
        }
    }


    @Override
    public int find(byte[] buffer, long offset, int length) throws ProxyCacheException, IOException {
        synchronized (dataFile) {
            // 数据边缓存变读取 当前缓存区间缓存完毕需要合并缓存区间并更新读写信息
            try {
                dataFile.seek(offset);
                int read = dataFile.read(buffer, 0, length);
                return read;

            } catch (IOException e) {
                String format = "Error reading %d bytes with offset %d from file[%d bytes] to buffer[%d bytes]";
                throw new ProxyCacheException(String.format(format, length, offset, offset + length,
                        buffer.length), e);
            }
        }
    }

    /**
     * 每次一个区间的数据写入完毕 或者写入线程关闭的时候
     * 更新一下本地的数据范围，
     *
     * @param offset 已下载区域合并位置
     */
    public void mergeWriteArea(long offset) {
        if (!isTempFile(file) || totalWriteSize == 0) {
            return;
        }
        if (fileLength == Long.MAX_VALUE) {
            return;
        }
        addNewAreaToSP(cacheStart, (cacheStart + totalWriteSize));
        cacheArr = getSpCacheArr();
        if (cacheArr == null) {
            return;
        }
        for (int i = 0; i < cacheArr.length; i++) {
            long[] longs = cacheArr[i];
            if (longs[0] < offset && offset < longs[1]) {
                int tempIndex = i + 1;
                if (tempIndex < cacheArr.length) {
                    long[] longs1 = cacheArr[tempIndex];
                    cacheStart = longs[0];
                    cacheEnd = longs1[0];
                } else {
                    cacheStart = longs[0];
                    cacheEnd = fileLength;
                }
                totalWriteSize = longs[1] - longs[0];
                break;
            }
        }
    }

    @Override
    public void insert(byte[] data, int length, long offset) throws ProxyCacheException, IOException {
        synchronized (dataFile) {
            try {
                if (isCompleted()) {
                    throw new ProxyCacheException("Error append cache: cache file " + file + " is completed!");
                }

                dataFile.seek(offset);
                if (isStop) {
                    return;
                }
                dataFile.write(data, 0, length);
                totalWriteSize += length;
            } catch (IOException e) {
                String format = "Error writing %d bytes to %s from buffer with size %d    " + e.toString();
                throw new ProxyCacheException(String.format(format, length, cacheStart,
                        (cacheStart + totalWriteSize)), e);
            }
        }
    }

    /**
     * 将本次已缓存的区域的起止点存入XML  类似于安卓的SharedPreferences
     *
     * @param sizeStart 本次已缓存的区域的起点
     * @param sizeEnd   本次已缓存的区域的终点
     */
    private synchronized void addNewAreaToSP(long sizeStart, long sizeEnd) {
        long tempEnd = sizeEnd;
        if (!isTempFile(file)) {
            return;
        }
        if (sizeStart == Long.MAX_VALUE || tempEnd == Long.MAX_VALUE) {
            return;
        }
        // 除了文件起始的{0 0}和文件结尾{fileLength fileLength} 其他的如果起止点一样不予存储
        if (sizeStart == tempEnd && sizeStart != 0 && sizeStart != fileLength) {
            return;
        }
        if (preferences == null) {
            return;
        }
        // 已缓存的区域的终点超过视频文件长度的话 设为视频长度
        if (fileLength > 0 && tempEnd > fileLength) {
            tempEnd = fileLength;
        }
        String gson = preferences.getString(Constants.VIDEO_CACHE_SP, "");
        if (TextTool.isNullOrEmpty(gson)) {
            // 首次存入文件起始的{0 0}和文件结尾{fileLength fileLength}
            cacheArr = new long[][]{new long[]{0, 0}, new long[]{sizeStart, tempEnd}};
            preferences.putString(Constants.VIDEO_CACHE_SP, new Gson().toJson(cacheArr)).flush();
        } else {
            cacheArr = new Gson().fromJson(gson, new TypeToken<long[][]>() {
            }.getType());
            List<long[]> list = new ArrayList<>();
            for (int i = 0; i < cacheArr.length; i++) {
                list.add(cacheArr[i]);
            }
            list.add(new long[]{sizeStart, tempEnd});
            long[][] merge = Solution.merge(list.toArray(new long[list.size()][2]));
            preferences.putString(Constants.VIDEO_CACHE_SP, new Gson().toJson(merge)).flush();
        }
    }

    @Override
    public boolean isCacheEnough(long offset, int length) throws IOException {
        if (!isTempFile(file)) {
            return true;
        }
        // 到文件结尾了 不需要再写入数据
        if (offset >= fileLength && fileLength > 0) {
            return true;
        }
        // 到文件结尾了 不需要再写入数据
        if (cacheStart + totalWriteSize >= fileLength && fileLength > 0) {
            return true;
        }
        // 读取的进度超过了写入的进度 需要先写入数据
        if (offset + length > cacheEnd) {
            if (cacheEnd == fileLength && fileLength > 0
                    && (cacheStart + totalWriteSize) >= fileLength) {
                return true;
            } else {
                return false;
            }
        }
        return offset + length <= cacheStart + totalWriteSize;
    }

    @Override
    public long getDownLoadStart() throws ProxyCacheException {
        // 网络请求视频的起始值
        return cacheStart + totalWriteSize;
    }

    @Override
    public long getDownLoadEnd() {
        // 网络请求视频长度的结束值
        return cacheEnd;
    }

    @Override
    public long getTotalCacheLength() {
        // 获取所有已缓存区域的起止点 将所有的间距相加得到总的缓存长度
        if (!isTempFile(file)) {
            return file.length();
        }
        long totalLength = 0;
        totalLength += totalWriteSize;
        if (cacheArr != null) {
            for (int i = 0; i < cacheArr.length; i++) {
                long[] longs = cacheArr[i];
                if (longs[0] == longs[1]) {
                    continue;
                } else if (longs[0] == cacheStart) {
                    continue;
                } else {
                    totalLength += (longs[1] - longs[0]);
                }
            }
        }
        return totalLength;
    }

    /**
     * 获取当前已下载区域的进度
     * 用于显示分节点存储的进度值
     *
     * @return 所有已下载区域
     */
    public List<long[]> getDownloadArea() {
        List<long[]> downLoadArea = new ArrayList<>();
        if (!isTempFile(file)) {
            downLoadArea.add(new long[]{0, file.length()});
            return downLoadArea;
        }
        if (cacheArr == null) {
            if (totalWriteSize > 0) {
                long[] longs1 = new long[2];
                longs1[0] = cacheStart;
                longs1[1] = cacheStart + totalWriteSize;
                downLoadArea.add(longs1);
            }
            return downLoadArea;
        }
        if (totalWriteSize > 0) {
            long[] longs1 = new long[2];
            longs1[0] = cacheStart;
            longs1[1] = cacheStart + totalWriteSize;
            downLoadArea.add(longs1);
        }
        for (int i = 0; i < cacheArr.length; i++) {
            long[] longs = cacheArr[i];
            if (longs[0] == longs[1]) {
                continue;
            } else if (longs[0] == cacheStart) {
                continue;
            } else {
                downLoadArea.add(longs);
            }
        }
        return downLoadArea;
    }

    @Override
    public void setStop(boolean isStop) {
        this.isStop = isStop;
    }
}
